package de.tuberlin.dima.minidb.impl.parser;

import de.tuberlin.dima.minidb.parser.Column;
import de.tuberlin.dima.minidb.parser.DeleteQuery;
import de.tuberlin.dima.minidb.parser.FromClause;
import de.tuberlin.dima.minidb.parser.GroupByClause;
import de.tuberlin.dima.minidb.parser.HavingClause;
import de.tuberlin.dima.minidb.parser.InsertQuery;
import de.tuberlin.dima.minidb.parser.IntegerLiteral;
import de.tuberlin.dima.minidb.parser.OrderByClause;
import de.tuberlin.dima.minidb.parser.OrderColumn;
import de.tuberlin.dima.minidb.parser.OutputColumn;
import de.tuberlin.dima.minidb.parser.ParseException;
import de.tuberlin.dima.minidb.parser.ParsedQuery;
import de.tuberlin.dima.minidb.parser.Predicate;
import de.tuberlin.dima.minidb.parser.RealLiteral;
import de.tuberlin.dima.minidb.parser.SQLParser;
import de.tuberlin.dima.minidb.parser.SQLTokenizer;
import de.tuberlin.dima.minidb.parser.SelectClause;
import de.tuberlin.dima.minidb.parser.SelectQuery;
import de.tuberlin.dima.minidb.parser.StringLiteral;
import de.tuberlin.dima.minidb.parser.TableReference;
import de.tuberlin.dima.minidb.parser.Token;
import de.tuberlin.dima.minidb.parser.ValuesClause;
import de.tuberlin.dima.minidb.parser.WhereClause;
import de.tuberlin.dima.minidb.parser.OrderColumn.Order;
import de.tuberlin.dima.minidb.parser.OutputColumn.AggregationType;
import de.tuberlin.dima.minidb.parser.ParseException.ErrorCode;
import de.tuberlin.dima.minidb.parser.Predicate.Operator;
import de.tuberlin.dima.minidb.parser.Token.TokenType;

/**
 * Implementation of the SQLParser interface.
 * @author thuydang
 * @author Trung
 * @author vicky
 */

public class ParserImpl implements SQLParser
{

	/**
	 * The statement for this parser.
	 */
	protected final String sqlStatement; 

	/**
	 * Creates a plain parser for the statement that starts at position 0.
	 * 
	 * @param statement The SQL statement to be parsed.
	 */
	public ParserImpl(String statement)
	{
		this.sqlStatement = statement;
	}

	/* (non-Javadoc) 
	 * @see de.tuberlin.dima.minidb.parser.SQLParser#parse()
	 */
	/**
	 * parse()
	 * TODO Done
	 */
	public ParsedQuery parse() throws ParseException
	{
		ParsedQuery parsedQuery = null;
		// instantiate tokenizer
		SQLTokenizer tokenizer = new SQLTokenizer(sqlStatement);
		SQLTokenizer tok = new SQLTokenizer(sqlStatement);

		Token to;
		while ((to = tok.nextToken()).getType() != TokenType.END_OF_STATEMENT){
			System.out.println(to.getTokenString() + " : " + to.getType().getStringRepresentation());
		}
		
		// get look-ahead token
		Token t = tokenizer.nextToken();
		// case distinction represents alternative choices in the <query> grammar rule
		if (t.getType() == TokenType.SELECT) {
			try {
				parsedQuery = parseSelectQuery(tokenizer);
			} catch (ParseException e){
				System.err.println(e.getMessage());
				throw e;
			}
		}
		else if (t.getType() == TokenType.INSERT) {
			try{
				parsedQuery = parseInsertQuery(tokenizer);
			} catch (ParseException e){
				System.err.println(e.getMessage());
				throw e;
			}
		}
		else if (t.getType() == TokenType.DELETE) {
			try {
				parsedQuery = parseDeleteQuery(tokenizer);
			} catch (ParseException e){
				System.err.println(e.getMessage());
				throw e;
			}
		}
		else {
			ParseException e = new ParseException(sqlStatement, ErrorCode.INVALID_CLAUSE, 
					tokenizer.getLastPosition(), t.getTokenString(), "SELECT,INSERT,DELETE");
			System.err.println(e.getMessage());
			throw new ParseException("Invalid query", ErrorCode.INVALID_CLAUSE, 
					tokenizer.getLastPosition(), t.getTokenString(), "SELECT,INSERT,DELETE");
		}
		return parsedQuery;
	}

	/**
	 * constructing root-node for selectQuery and return.
	 * called immediately after SELECT is parsed. Last used token SELECT.
	 * @param t tokenized query string 
	 * @throws Exception 
	 */
	private SelectQuery parseSelectQuery(SQLTokenizer t) throws ParseException {
		SelectQuery select = new SelectQuery();

		/*
		 * check query against grammar
		 *	<select_query> = <select_clause> <from_clause> 
		 *				[<where_clause>][<group_by_cls>] [<having_clause>] [<order_by_cls>]
		 * <select_clause> = SELECT <out_column> {, <out_column>}*
		 * <from_clause> =   FROM <table_ref> {, <table_ref>}*
		 * <where_clause> =  WHERE <predicate> {AND <predicate>}*
		 * <group_by_cls> =  GROUP BY <column> {, <column>}*
		 * <having_clause> = HAVING <hav_pred> {AND <hav_pred>}*
		 * <order_by_cls> =  ORDER BY <order_column> {, <order_column> }*
		 */

		// parse SelectClause
		SelectClause selectClause = parseSelectClause(t);
		select.setSelectClause(selectClause);
		System.out.println("check point ** SELECT CLAUSE **");

		// Parse FromClause
		// From Clause is obligatory so check for its existence
		FromClause fromClause = parseFromClause(t);
		select.setFromClause(fromClause);
		System.out.println("check point ** FROM **");

		/* 
		 * ### Optional Clauses
		 * Select query can end here or have optional clauses: where, group by, having, order by.
		 * They should if exist end with the first token of the next clause as last used token.
		 */
		while (t.getLastToken().getType() != TokenType.END_OF_STATEMENT){
			/*
			 * ### Where clause
			 */
			if (t.getLastToken().getType() == TokenType.WHERE) {
				try {
					WhereClause whereClause = parseWhereClause(t);						
					select.setWhereClause(whereClause);
					System.out.println("check point: ** WHERE **");
				} catch (ParseException e) {
					throw e;
				}
			}
			// * after FROM clause as well as after each iteration, lastToken will be the first token of the following clause

			/*
			 * ### Group by clause
			 */
			else if (t.getLastToken().getType() == TokenType.GROUP) {
				if (t.nextToken().getType() != TokenType.BY)
					throw new ParseException("BY expected", ErrorCode.INVALID_CLAUSE, t.getLastPosition(), 
							t.getLastToken().getTokenString(), TokenType.BY.getStringRepresentation());	
				try {
					GroupByClause groupByClause = parseGroupClause(t);						
					select.setGroupByClause(groupByClause);
					System.out.println("check point: ** GROUP BY **");
				} catch (ParseException e) {
					throw e;
				}
			}

			/* ### Having Clause
			 * after FROM clause as well as after each iteration, lastToken will be the first token of the following clause
			 */
			else if (t.getLastToken().getType() == TokenType.HAVING) {
				try {
					HavingClause havingClause = parseHavingClause(t);
					select.setHavingClause(havingClause);
					System.out.println("check point: ** HAVING **");
				} catch (ParseException e) {
					throw e;
				}
			}
			/*
			 * ### Order by Clause
			 */
			else if (t.getLastToken().getType() == TokenType.ORDER) {
				if (t.nextToken().getType() != TokenType.BY)
					throw new ParseException(sqlStatement, ErrorCode.INVALID_CLAUSE,
							t.getLastPosition(), t.getLastToken().getTokenString(), TokenType.BY.getStringRepresentation());	
				try {
					OrderByClause orderByClause = parseOrderClause(t);
					select.setOrderByClause(orderByClause);
					System.out.println("check point: ** ORDER BY **");
				} catch (ParseException e){
					throw e;
				}
			}
			/*
			 * end statement
			 */
			else throw new ParseException(sqlStatement, ErrorCode.INVALID_CLAUSE, t.getLastPosition(), 
					t.getLastToken().getTokenString(), TokenType.END_OF_STATEMENT.getStringRepresentation());
		}
		return select;
	}

	/*
	 * <begin> functions used by parseSelectQuery()
	 */

	/*
	 * last used token is HAVING 
	 */
	private HavingClause parseHavingClause(SQLTokenizer tokenizer) throws ParseException {
		Token token;
		Predicate pred = new Predicate();
		OutputColumn outputColumn;
		Operator operator;
		boolean exit = false;

		HavingClause havingClause = new HavingClause();

		while (!exit){
			// token is the first Token after having
			token = tokenizer.nextToken();
			// leftSide
			if (token.getType() != TokenType.IDENTIFIER)
				throw new ParseException("Identifier expected", ErrorCode.INVALID_CLAUSE);
			outputColumn = new OutputColumn(null, token.getTokenString());
			pred.setLeftHandSide(outputColumn);

			// operator
			token = tokenizer.nextToken();
			if (token.getType() == TokenType.OPERAND_EQUAL) {
				operator = Operator.EQUAL;
			} else if (token.getType() == TokenType.OPERAND_GREATER_EQUAL) {
				operator = Operator.GREATER_OR_EQUAL;
			} else if (token.getType() == TokenType.OPERAND_GREATER_THAN) {
				operator = Operator.GREATER;
			} else if (token.getType() == TokenType.OPERAND_SMALLER_EQUAL) {
				operator = Operator.SMALLER_OR_EQUAL;
			} else if (token.getType() == TokenType.OPERAND_SMALLER_THAN) {
				operator = Operator.SMALLER;
			} else if (token.getType() == TokenType.OPERAND_UNEQUAL) {
				operator = Operator.NOT_EQUAL;
			} else { 	
				throw new ParseException(sqlStatement, ErrorCode.INVALID_OPERAND,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.OPERAND_EQUAL + "...");
			}
			pred.setOperator(operator);

			// value
			token = tokenizer.nextToken();
			System.out.println("### trap" + token.getType());
			//throw new ParseException("Value expected", ErrorCode.INVALID_OPERAND);

			if (token.getType() == TokenType.INTEGER_NUMBER) {
				//				pred.setRighHandSide(new IntegerLiteral(Integer.parseInt(token.getTokenString())));
				pred.setRighHandSide(new IntegerLiteral(Long.valueOf(token.getTokenString()).longValue()));
			} else if (token.getType() == TokenType.REAL_NUMBER) {
				pred.setRighHandSide(new RealLiteral(Double.valueOf(token.getTokenString()).doubleValue()));
			} else if (token.getType() == TokenType.LITERAL) {
				pred.setRighHandSide(new StringLiteral(token.getTokenString()));
			} else {
				throw new ParseException(sqlStatement, ErrorCode.INVALID_PREDICATE_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.VALUES.getStringRepresentation());
			}
			havingClause.addPredicate(pred);

			if (tokenizer.nextToken().getType() != TokenType.AND){ 
				exit = true;
				// end parseHavingClause with last used token the first one of next clause.
			}
		}

		return havingClause;
	}

	/*
	 * parseOrderClause begins with "BY" as the last used token
	 * 					ends with first token of the following clause as last used token.  
	 */
	private OrderByClause parseOrderClause(SQLTokenizer tokenizer) throws ParseException {
		OrderByClause oderClause = new OrderByClause();
		Token token;
		boolean exit = false;

		// loop through output columns
		while (exit == false){
			// normal column table.name
			token = tokenizer.nextToken();
			if (token.getType() != TokenType.IDENTIFIER) 
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());

			// create OrderColumn(OutputColumn_name) for "ORDER BY column"
			OutputColumn outputColumn = new OutputColumn(null, token.getTokenString());
			OrderColumn orderColumn;

			// check the existence of the optionals ASC | DSC
			// next token can be asc | dsc | comma | next clause
			token = tokenizer.nextToken();
			if (token.getType() == TokenType.ASCENDING ) {
				orderColumn = new OrderColumn(outputColumn, Order.ASCENDING);
				// next token can only be comma | next clause
				tokenizer.nextToken();
			} else if ((token.getType() == TokenType.DESCENDING) ) {
				orderColumn = new OrderColumn(outputColumn, Order.DESCENDING);
				// next token can only be comma | next clause
				tokenizer.nextToken();
			} else if ( (token.getType() == TokenType.COMMA) || 
					(token.getType() == TokenType.GROUP) ||
					(token.getType() == TokenType.END_OF_STATEMENT) ||
					(token.getType() == TokenType.HAVING) ){
				orderColumn = new OrderColumn(outputColumn);
				oderClause.addOrderColumn(orderColumn);
			} else {
				System.out.println("*****************");
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());
			}

			oderClause.addOrderColumn(orderColumn);
			// check last token for comma | next clause
			if (tokenizer.getLastToken().getType() != TokenType.COMMA){ 
				exit = true;
				// end parseOderClause with last used token the first one of next clause.
				tokenizer.nextToken();
			}	
		}
		return oderClause;
	}

	/*
	 * parseGroupClause terminates and last used token is the first token of the following clause
	 */
	private GroupByClause parseGroupClause(SQLTokenizer tokenizer) throws ParseException {
		GroupByClause groupClause = new GroupByClause();
		Token token, t1, t2;
		boolean exit = false;

		// loop through output columns
		while (exit == false){
			token = tokenizer.nextToken();
			// normal column table.name
			if (token.getType() != TokenType.IDENTIFIER) 
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT, tokenizer.getLastPosition(),
						token.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());

			// create Column(column_name, table_name) for "table_name.column_name"
			t1 = token;
			if (tokenizer.nextToken().getType() != TokenType.PERIOD) 
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT, tokenizer.getLastPosition(),
						token.getTokenString(), TokenType.PERIOD.getStringRepresentation());
			t2 = tokenizer.nextToken();
			Column column = new Column(t2.getTokenString(), t1.getTokenString());
			groupClause.addColumn(column);

			//*note: when there is no comma, groupClause terminate and current token will be the one next to tableAlias
			if (tokenizer.nextToken().getType() != TokenType.COMMA) exit = true;
		}
		return groupClause;
	}

	/**
	 * parseFromClause terminates and last used token is the first token of the following Clause
	 */

	private FromClause parseFromClause(SQLTokenizer tokenizer) throws ParseException{
		FromClause fromClause = new FromClause();
		boolean exit = false;
		Token token, tableNameToken, tableAliasToken;

		token = tokenizer.getLastToken();
		if (token.getType() != TokenType.FROM)
			throw new ParseException(sqlStatement, ErrorCode.UNKNOWN_KEYWORD, tokenizer.getLastPosition(),
					token.getTokenString(), TokenType.FROM.getStringRepresentation());
		
		while (!exit){
			tableNameToken = tokenizer.nextToken();
			if (tableNameToken.getType() != TokenType.IDENTIFIER)
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), tableNameToken.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());

			tableAliasToken = tokenizer.nextToken();
			if (tableAliasToken.getType() != TokenType.IDENTIFIER)
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), tableAliasToken.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());

			TableReference tableRef = new TableReference(tableNameToken.getTokenString(), tableAliasToken.getTokenString());
			fromClause.addTable(tableRef);
			//*note: when there is no comma, parseFromClause terminate and current token will be the one next to tableAlias
			if (tokenizer.nextToken().getType() != TokenType.COMMA) {
				exit = true;
			} //else tokenizer.nextToken(); <-- here is the bug
		}
		return fromClause;
	}

	
	private SelectClause parseSelectClause(SQLTokenizer tokenizer) throws ParseException{
		SelectClause selectClause = new SelectClause();
		boolean exit = false;

		// loop through output columns
		while (exit == false){
			selectClause.addOutputColumn(parseOutColumn(tokenizer));
			
			// check for continue condition:
			// suppose last used token is <IDENTIFIER>
			if (tokenizer.nextToken().getType() != TokenType.COMMA) exit = true;
		}
						//*note: if SelectClause ends here, current token will be the one next to SelectClause
		
		// Select clause ends
		return selectClause;
	}

	private OutputColumn parseOutColumn(SQLTokenizer tokenizer) throws ParseException {
		OutputColumn outColumn;
		// lookahead for <column> or <aggFunction>
		Token token = tokenizer.nextToken();

		if (token.getType() == TokenType.IDENTIFIER) {
			// last used token is identifier
			Column column = parseColumn(tokenizer);

			token = tokenizer.nextToken();
			if ( token.getType() != TokenType.AS) throw new ParseException(sqlStatement, ErrorCode.UNKNOWN_KEYWORD,
					tokenizer.getLastPosition(), token.getTokenString(), TokenType.AS.getStringRepresentation());

			return outColumn = new OutputColumn(column, tokenizer.nextToken().getTokenString());
		// aggregate function	
		} else {
			AggregationType aggType;
			if (token.getType() == TokenType.AGG_AVG) aggType = AggregationType.AVG;
			else if (token.getType() == TokenType.AGG_COUNT) aggType = AggregationType.COUNT;
			else if (token.getType() == TokenType.AGG_MAX) aggType = AggregationType.MAX;
			else if (token.getType() == TokenType.AGG_MIN) aggType = AggregationType.MIN;
			else if (token.getType() == TokenType.AGG_SUM) aggType = AggregationType.SUM;
			else throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT, tokenizer.getLastPosition(), 
					token.getType().getStringRepresentation(), TokenType.AGG_AVG.getStringRepresentation() + "...");
			
			if (tokenizer.nextToken().getType() != TokenType.PARENTHESIS_OPEN)
				throw new ParseException(sqlStatement, ErrorCode.INVALID_CHARACTER, tokenizer.getLastPosition(), 
						token.getType().getStringRepresentation(), TokenType.PARENTHESIS_OPEN.getStringRepresentation());
			
			token = tokenizer.nextToken();
			Column column = parseColumn(tokenizer);

			if (tokenizer.nextToken().getType() != TokenType.PARENTHESIS_CLOSE) 
				throw new ParseException(sqlStatement, ErrorCode.INVALID_CHARACTER, tokenizer.getLastPosition(), 
						token.getType().getStringRepresentation(), TokenType.PARENTHESIS_CLOSE.getStringRepresentation());
			// TODO parse AS
			token = tokenizer.nextToken();
			if ( token.getType() != TokenType.AS) throw new ParseException(sqlStatement, ErrorCode.UNKNOWN_KEYWORD,
					tokenizer.getLastPosition(), token.getTokenString(), TokenType.AS.getStringRepresentation());

			// TODO parse <IDENTIFIER> and return outColumn
			return outColumn = new OutputColumn(column, tokenizer.nextToken().getTokenString(), aggType);
		}

	}

	// last used token is identifier
	private Column parseColumn(SQLTokenizer tokenizer) throws ParseException {
		// TODO parseColumn
		
		String tableAlias = parseIdentifier(tokenizer);
		
		// last used token is identifier
		parsePeriod(tokenizer);
		// last used token is identifier
		String columnName = parseIdentifier(tokenizer);
		Column column = new Column(columnName, tableAlias);
		return column;
	}

	/*
	 * <end> functions used by parseSelectQuery()
	 */

	private void parsePeriod(SQLTokenizer tokenizer) throws ParseException {
		Token token = tokenizer.nextToken();
		if (token.getType() != TokenType.PERIOD)
			throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT, 
					tokenizer.getLastPosition(),token.getTokenString(),TokenType.PERIOD.getStringRepresentation());
		tokenizer.nextToken();
	}

	// last used token = identifier
	private String parseIdentifier(SQLTokenizer tokenizer) throws ParseException {
		Token token = tokenizer.getLastToken();
		if (token.getType() != TokenType.IDENTIFIER)
			throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT, 
					tokenizer.getLastPosition(),token.getTokenString());		
		return token.getTokenString();
	}

	private InsertQuery parseInsertQuery(SQLTokenizer t) throws ParseException {
		InsertQuery insert = new InsertQuery();
		Token tokInto = t.nextToken();
		ValuesClause vc;
		if (tokInto.getType() == TokenType.INTO)
		{
			Token tokIndentifier = t.nextToken();
			if (tokIndentifier.getType() == TokenType.IDENTIFIER)
			{
				insert.setTableName(tokIndentifier.getTokenString());
				Token tokValues = t.nextToken();
				if (tokValues.getType() == TokenType.VALUES)
				{
					Token tok = t.nextToken();
					if (tok.getType() == TokenType.PARENTHESIS_OPEN)
					{
						vc = parseValuesClause(t);
						insert.addValuesClause(vc);
						tok = t.nextToken();
						System.out.println("token type 1 :" + tok.getType().toString());
						if (tok.getType() == TokenType.COMMA)
						{
							tok = t.nextToken();
							System.out.println("token type 2 :" + tok.getType().toString());
							vc = parseValuesClause(t);
							insert.addValuesClause(vc);
							tok = t.nextToken();
							if (tok.getType() != TokenType.END_OF_STATEMENT)
							{
								throw new ParseException(sqlStatement, ErrorCode.INVALID_TOKEN,
										t.getLastPosition(), tok.getTokenString(), TokenType.END_OF_STATEMENT.getStringRepresentation());
							}
						}
						else if (tok.getType() == TokenType.END_OF_STATEMENT)
						{
							// do nothing
						}
						else
						{
							throw new ParseException(sqlStatement, ErrorCode.INVALID_TOKEN,
									t.getLastPosition(), tok.getTokenString(), TokenType.END_OF_STATEMENT.getStringRepresentation());
						}
					}
				}
				else if (tokValues.getType() == TokenType.SELECT)
				{
						SelectQuery sq = parseSelectQuery(t);
						insert.setSelectQuery(sq);						
				}
				else
				{
					throw new ParseException(sqlStatement, ErrorCode.INVALID_TOKEN,
							t.getLastPosition(), t.getLastToken().getTokenString());
				}
			}
			else
			{
				throw new ParseException(sqlStatement, ErrorCode.INVALID_TOKEN,
						t.getLastPosition(), tokIndentifier.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());
			}
		}
		else
		{
			throw new ParseException(sqlStatement, ErrorCode.INVALID_TOKEN,
					t.getLastPosition(), tokInto.getTokenString(), TokenType.INTO.getStringRepresentation());
		}
		return insert;
	}

	private ValuesClause parseValuesClause(SQLTokenizer t) throws ParseException
	{
		ValuesClause vc = new ValuesClause();
		Token tokParenthesisOpen = t.getLastToken();
		long l;
		double d;
		String s;
		IntegerLiteral il;
		RealLiteral rl;
		StringLiteral sl;
		if (tokParenthesisOpen.getType() == TokenType.PARENTHESIS_OPEN)
		{
			Token tok = t.nextToken();
			Token tokComma;
			vc = new ValuesClause();
			while (tok.getType() != TokenType.PARENTHESIS_CLOSE)
			{
				if (tok.getType() == TokenType.COMMA)
				{
					tok = t.nextToken();
				}
				System.out.println("component type : " + tok.getType().toString());
				if (tok.getType() == TokenType.INTEGER_NUMBER)
				{
					l = Long.valueOf(tok.getTokenString()).longValue();
					il = new IntegerLiteral(l);
					vc.addValue(il);
					tokComma = t.nextToken();
					if (tokComma.getType() != TokenType.COMMA)
					{
						if (tokComma.getType() != TokenType.PARENTHESIS_CLOSE)
						{
							throw new ParseException();
						}	
					}	
					tokComma = t.nextToken();
					tok = tokComma;
				}
				else if (tok.getType() == TokenType.REAL_NUMBER)
				{
					d = Double.valueOf(tok.getTokenString()).doubleValue();
					rl = new RealLiteral(d);
					vc.addValue(rl);
					tokComma = t.nextToken();
					if (tokComma.getType() != TokenType.COMMA)
					{
						if (tokComma.getType() != TokenType.PARENTHESIS_CLOSE)
						{
							throw new ParseException();
						}
					}	
					tok = tokComma;
				}
				else if (tok.getType() == TokenType.LITERAL)
				{
					s = tok.getTokenString();
					sl = new StringLiteral(s);
					vc.addValue(sl);
					tokComma = t.nextToken();
					if (tokComma.getType() != TokenType.COMMA)
					{
						if (tokComma.getType() != TokenType.PARENTHESIS_CLOSE)
						{
							throw new ParseException();
						}	
					}	
					tok = tokComma;
					System.out.println("component type in Literal : " + tok.getType().toString());
				}
				else
				{
					throw new ParseException();
				}
			}
		}
		else
		{
			throw new ParseException();
		}
		return vc;
	}



	private DeleteQuery parseDeleteQuery(SQLTokenizer t) throws ParseException {

		// create parse tree node that represents this rule
		DeleteQuery del = new DeleteQuery();

		// check syntax: the DELETE token was look-ahead, next token must be FROM
		checkNextToken(t, TokenType.FROM);

		// Get lookahead token and check that it is an identifier. This is
		// consistent with the idea that every function was called after one
		// token has been taken as a lookahead.
		//
		// as an alternative, you can just call the rule directly,
		// because there is not really a choice to be made, but then the
		// rule would need to be aware that the first token has not yet been
		// fetched
		Token tok = t.nextToken();
		if (tok.getType() == TokenType.IDENTIFIER) {
			TableReference ref = parseTableRef(t);
			del.setTable(ref);
		}
		else {
			// exception
			throw new ParseException();
		}

		// look ahead to see if a where clause comes, or if we are done
		tok = t.nextToken();
		if (tok.getType() == TokenType.WHERE) {
			System.out.println("### start parseWhereClause");
			WhereClause w = parseDelWhereClause(t);
			if (t.getLastToken().getType() != TokenType.END_OF_STATEMENT)
				throw new ParseException(sqlStatement, ErrorCode.INVALID_TOKEN,
						t.getLastPosition(), tok.getTokenString(), TokenType.END_OF_STATEMENT.getStringRepresentation());
			del.setWhere(w);
		}
		else if (tok.getType() == TokenType.END_OF_STATEMENT) {
			// all done, do nothing
		}
		else {
			throw new ParseException(sqlStatement, ErrorCode.UNKNOWN_KEYWORD,
					t.getLastPosition(), tok.getTokenString());
		}

		// give back the node for this rule
		return del;
	}

	private WhereClause parseDelWhereClause(SQLTokenizer tokenizer) throws ParseException {
		WhereClause whereClause = new WhereClause();
		// token after where
		Token token = tokenizer.nextToken();
		Token t1, t2;
		Operator operator;
		Predicate pred = new Predicate();
		Column column;
		boolean exit = false;

		while (!exit){
			// last used token is <identifier>
			//token = tokenizer.getLastToken();
			if (token.getType() != TokenType.IDENTIFIER)
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());

			// left Side
			// create Column(column_name, table_name) for "table_name.column_name"
			t1 = token;
			if (tokenizer.nextToken().getType() != TokenType.PERIOD) 
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.PERIOD.getStringRepresentation());

			t2 = tokenizer.nextToken();
			column = new Column(t2.getTokenString(), t1.getTokenString());
			pred.setLeftHandSide(column);

			// parse operator
			token = tokenizer.nextToken();
			if (token.getType() == TokenType.OPERAND_EQUAL) {
				operator = Operator.EQUAL;
			} else if (token.getType() == TokenType.OPERAND_GREATER_EQUAL) {
				operator = Operator.GREATER_OR_EQUAL;
			} else if (token.getType() == TokenType.OPERAND_GREATER_THAN) {
				operator = Operator.GREATER;
			} else if (token.getType() == TokenType.OPERAND_SMALLER_EQUAL) {
				operator = Operator.SMALLER_OR_EQUAL;
			} else if (token.getType() == TokenType.OPERAND_SMALLER_THAN) {
				operator = Operator.SMALLER;
			} else if (token.getType() == TokenType.OPERAND_UNEQUAL) {
				operator = Operator.NOT_EQUAL;
			} else { 	
				throw new ParseException(sqlStatement, ErrorCode.INVALID_OPERAND,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.OPERAND_EQUAL.getStringRepresentation() +"...");

			}
			pred.setOperator(operator);

			// right side <column> or <value>
			// parse
			token = tokenizer.nextToken();
			//here comes no <column>
			
				//here comes a <value>
			if (token.getType() == TokenType.INTEGER_NUMBER) {
				pred.setRighHandSide(new IntegerLiteral(Long.valueOf(token.getTokenString()).longValue()));
			} else if (token.getType() == TokenType.REAL_NUMBER) {
				pred.setRighHandSide(new RealLiteral(Double.valueOf(token.getTokenString()).doubleValue()));
			} else if (token.getType() == TokenType.LITERAL) {
				pred.setRighHandSide(new StringLiteral(token.getTokenString()));
			} else
				throw new ParseException(sqlStatement, ErrorCode.INVALID_PREDICATE_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.LITERAL.getStringRepresentation());

			whereClause.addPredicate(pred);
			token = tokenizer.nextToken();
			// * new predicate
			pred = new Predicate();
			if (token.getType() != TokenType.AND) {
				exit = true;
				// ends and last used token will be the one after <predicate> 
			} else {
				token = tokenizer.nextToken();
			}
		}
		return whereClause;

	}

	/*
	 * last used token is Where
	 * Where identifier.identifier op identifier.identifier | identifier.identifier op value 
	 */
	private WhereClause parseWhereClause(SQLTokenizer tokenizer) throws ParseException {
		WhereClause whereClause = new WhereClause();
		// token after where
		Token token = tokenizer.nextToken();
		Token t1, t2;
		Operator operator;
		Predicate pred = new Predicate();
		Column column;
		boolean exit = false;

		while (!exit){
			// last used token is <identifier>
			//token = tokenizer.getLastToken();
			if (token.getType() != TokenType.IDENTIFIER)
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.IDENTIFIER.getStringRepresentation());

			// left Side
			// create Column(column_name, table_name) for "table_name.column_name"
			t1 = token;
			if (tokenizer.nextToken().getType() != TokenType.PERIOD) 
				throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.PERIOD.getStringRepresentation());

			t2 = tokenizer.nextToken();
			column = new Column(t2.getTokenString(), t1.getTokenString());
			pred.setLeftHandSide(column);

			// parse operator
			token = tokenizer.nextToken();
			if (token.getType() == TokenType.OPERAND_EQUAL) {
				operator = Operator.EQUAL;
			} else if (token.getType() == TokenType.OPERAND_GREATER_EQUAL) {
				operator = Operator.GREATER_OR_EQUAL;
			} else if (token.getType() == TokenType.OPERAND_GREATER_THAN) {
				operator = Operator.GREATER;
			} else if (token.getType() == TokenType.OPERAND_SMALLER_EQUAL) {
				operator = Operator.SMALLER_OR_EQUAL;
			} else if (token.getType() == TokenType.OPERAND_SMALLER_THAN) {
				operator = Operator.SMALLER;
			} else if (token.getType() == TokenType.OPERAND_UNEQUAL) {
				operator = Operator.NOT_EQUAL;
			} else { 	
				throw new ParseException(sqlStatement, ErrorCode.INVALID_OPERAND,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.OPERAND_EQUAL.getStringRepresentation() +"...");

			}
			pred.setOperator(operator);

			// right side <column> or <value>
			// parse
			token = tokenizer.nextToken();
			//here comes a <column>
			if (token.getType() == TokenType.IDENTIFIER) {
				// create Column(column_name, table_name) for "table_name.column_name"
				t1 = token;
				if (tokenizer.nextToken().getType() != TokenType.PERIOD) 
					throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
							tokenizer.getLastPosition(), token.getTokenString(), TokenType.PERIOD.getStringRepresentation());
				t2 = tokenizer.nextToken();
				column = new Column(t2.getTokenString(), t1.getTokenString());
				pred.setRighHandSide(column);
				//here comes a <value>
			} else if (token.getType() == TokenType.INTEGER_NUMBER) {
				pred.setRighHandSide(new IntegerLiteral(Long.valueOf(token.getTokenString()).longValue()));
			} else if (token.getType() == TokenType.REAL_NUMBER) {
				pred.setRighHandSide(new RealLiteral(Double.valueOf(token.getTokenString()).doubleValue()));
			} else if (token.getType() == TokenType.LITERAL) {
				pred.setRighHandSide(new StringLiteral(token.getTokenString()));
			} else
				throw new ParseException(sqlStatement, ErrorCode.INVALID_PREDICATE_FORMAT,
						tokenizer.getLastPosition(), token.getTokenString(), TokenType.LITERAL.getStringRepresentation());

			whereClause.addPredicate(pred);
			token = tokenizer.nextToken();
			// * new predicate
			pred = new Predicate();
			if (token.getType() != TokenType.AND) {
				exit = true;
				// ends and last used token will be the one after <predicate> 
			} else {
				token = tokenizer.nextToken();
			}
		}
		return whereClause;
	}

	// choose the operator based on the token-type
	private Operator setOperatorFromTokenType(TokenType tt) throws ParseException 
	{
		Operator op;
		if (tt == TokenType.OPERAND_EQUAL) 
		{
			op = Operator.EQUAL;
		}
		else if (tt == TokenType.OPERAND_GREATER_EQUAL)
		{
			op = Operator.GREATER_OR_EQUAL;
		}
		else if (tt == TokenType.OPERAND_GREATER_THAN)
		{
			op = Operator.GREATER;
		}
		else if (tt == TokenType.OPERAND_SMALLER_EQUAL)
		{
			op = Operator.SMALLER_OR_EQUAL;
		}
		else if (tt == TokenType.OPERAND_SMALLER_THAN)
		{
			op = Operator.SMALLER;
		}
		else if (tt == TokenType.OPERAND_UNEQUAL)
		{
			op = Operator.NOT_EQUAL;
		}
		else
		{
			throw new ParseException();
		}
		return op;
	}

	private TableReference parseTableRef(SQLTokenizer t) throws ParseException
	{
		Token t1 = t.getLastToken(); // get the last token, which was the look-ahead-token
		Token t2 = t.nextToken();    // next token, representa table alias

		if (t1.getType() == TokenType.IDENTIFIER && t2.getType() == TokenType.IDENTIFIER) {
			return new TableReference(t1.getTokenString(), t2.getTokenString());
		}
		else {
			// exception
			throw new ParseException(sqlStatement, ErrorCode.INVALID_COLUMN_FORMAT,
					t.getLastPosition(), t1.getTokenString() );
		}

		// an alternative for the method could be to rely on tha fact that the lookahead type
		// has already been checked and use the utility function
		// then the whole method body would be a single line:
		//
		// return new TableReference(t.getLastToken().getTokenString(), checkNextToken(t, TokenType.IDENTIFIER));
	}

	private String checkNextToken(SQLTokenizer t, TokenType type) throws ParseException {
		Token tok = t.nextToken();
		if (tok.getType() == type) {
			return tok.getTokenString();
		}
		throw new ParseException();
	}
}

